/*
 *  Copyright 2020-2025 the original author or authors.
 *  You cannot use this file unless authorized by the author.
 */

package org.ipig.search.es;

import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.ipig.search.DocInfo;
import org.ipig.search.es.parm.SearchParm;
import org.ipig.search.es.result.SearchResult;


import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

/**
 * doc 管理
 *
 * @author <a href="mailto:comchnts@163.com">chinats</a>
 * @version $$Id$$
 * @since 1.0
 */
@Slf4j
public class DocManager {
    /** type */
    public static final String TYPE = "type";
    /** 默认删除数量 */
    public static final int TOTAL_HITS = 500;

    /**
     * @param client
     *            Client
     * @param doc
     *            DocInfo
     * @return IndexRequest
     * @throws Exception
     */
    public static IndexRequest buildIndexRequest(Client client, final DocInfo doc) throws Exception {
        IndexRequest indexRequest = client.prepareIndex(doc.getIndexName(), doc.getIndexTypeName(), doc.getDocId())
                .setSource(doc.getKvFieldMap()).request();
        return indexRequest;
    }

    /**
     * 构建删除请求
     *
     * @param client
     *            Client
     * @param doc
     *            DocInfo
     * @return DeleteRequest
     */
    public static DeleteRequest buildDeleteRequest(Client client, final DocInfo doc) {
        DeleteRequest deleteRequest = client.prepareDelete(doc.getIndexName(), doc.getIndexTypeName(), doc.getDocId())
                .request();
        return deleteRequest;
    }

    /**
     * 打印索引
     *
     * @param doc
     *            DocInfo
     */
    public static void print(final DocInfo doc) {
        log.debug("{}/{}/{}索引 数据:{}", doc.getIndexName(), doc.getIndexTypeName(), doc.getDocId(),
                (new Gson()).toJson(doc.getKvFieldMap()));
    }

    /**
     * 构建批量操作请求
     *
     * @param client
     *            Client
     * @param docList
     *            List<DocInfo>
     * @return BulkRequestBuilder
     */
    public static BulkRequestBuilder buildBulkRequest(Client client, final List<DocInfo> docList) {
        BulkRequestBuilder bulkRequest = client.prepareBulk();
        for (DocInfo doc : docList) {
            try {
                if (DocInfo.DocOperate.D == doc.getDocOperate()) {
                    if (StringUtils.isNotBlank(doc.getDocId())) {
                        DeleteRequest deleteRequest = client
                                .prepareDelete(doc.getIndexName(), doc.getIndexTypeName(), doc.getDocId()).request();
                        bulkRequest.add(deleteRequest);
                    } else if (!doc.getKvFieldMap().isEmpty()) {
                        buildDeleteRequest(client, bulkRequest, doc);
                    }
                } else {
                    IndexRequest indexRequest = client
                            .prepareIndex(doc.getIndexName(), doc.getIndexTypeName(), doc.getDocId())
                            .setSource(doc.getKvFieldMap()).request();
                    bulkRequest.add(indexRequest);
                }
            } catch (Exception e) {
                log.warn("构建索引发生异常", e);
                log.error(ExceptionUtils.getRootCauseMessage(e));
            }
        }
        return bulkRequest;
    }

    /**
     * 构建删除请求
     *
     * @param client
     *            Client
     * @param bulkRequest
     *            BulkRequestBuilder
     * @param doc
     *            DocInfo
     */
    private static void buildDeleteRequest(Client client, BulkRequestBuilder bulkRequest, final DocInfo doc) {
        Map<String, Object> kv = doc.getKvFieldMap();
        Set<String> set = kv.keySet();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        for (String key : set) {
            boolQueryBuilder.must(QueryBuilders.termQuery(key, kv.get(key)));
        }
        QueryBuilder query = QueryBuilders.constantScoreQuery(boolQueryBuilder);
        SearchParm searchParm = new SearchParm();
        searchParm.setIndexName(doc.getIndexName());
        searchParm.setQueryBuilder(query);
        searchParm.setSize(TOTAL_HITS + 1);
        log.debug("QueryBuilder={}", query.toString());
         SearchResult searchResult = SearchManager.matchQuery(client, searchParm);
        SearchHits searchHits = searchResult.getSearchHits();

        if (searchHits != null) {
            long totalHits = searchHits.getTotalHits().value;
//			long totalHits = searchHits.getTotalHits();
            if (totalHits <= 0) {
                log.info("没有符合条件的索引文档构建,检索条件:{}", (new Gson()).toJson(searchParm));
                return;
            } else if (totalHits > TOTAL_HITS) {
                log.warn("【风险】禁止将>{}的记录进行删除，当前满足条件的删除记录{}，当前检索条件:{}", TOTAL_HITS, totalHits,
                        (new Gson()).toJson(searchParm));
                return;
            }
            searchHits.forEach(new Consumer<SearchHit>() {
                @Override
                public void accept(SearchHit hit) {
                    if (log.isDebugEnabled()) {
                        // 遍历文档的每个字段
                        Map<String, Object> map = hit.getSourceAsMap();
                        if (map != null && !map.isEmpty()) {
                            StringBuilder sb = new StringBuilder();
                            for (String key : map.keySet()) {
                                sb.append(key).append("=").append(map.get(key)).append(", ");
                            }
                            log.debug("indexName=【{}】，typeName=【{}】，docId=【{}】  source=【{}】", hit.getIndex(),
                                    hit.getType(), hit.getId(), sb.toString());
                        }
                    }
                    String docId = hit.getId();
                    if (StringUtils.isNotBlank(docId)) {
                        DeleteRequest deleteRequest = client
                                .prepareDelete(doc.getIndexName(), doc.getIndexTypeName(), docId).request();
                        bulkRequest.add(deleteRequest);
                        log.debug("DeleteRequest docId={}", docId);
                    }
                }
            });
        }
    }

    /**
     * 创建文档索引
     *
     * @param client
     *            Client
     * @param docList
     *            List<DocInfo>
     * @return boolean
     */
    public static boolean createIndexDoc(Client client, final List<DocInfo> docList) {
        boolean result = false;
        if (docList == null || docList.isEmpty()) {
            return result;
        }
        try {
            BulkRequestBuilder bulkRequest = buildBulkRequest(client, docList);
            int reqCount = bulkRequest.request().numberOfActions();
            if (reqCount > 0) {
                BulkResponse bulkResponse = bulkRequest.execute().actionGet();
                if (bulkResponse.hasFailures()) {
                    log.warn("当前构建文档索引发生异常：{}|{}", reqCount, bulkResponse.buildFailureMessage());
                }
                log.debug("当前构建文档索引数：{}", reqCount);
            }
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error(ExceptionUtils.getRootCauseMessage(e));
        }
        return result;
    }
}
